/*
* The MIT License (MIT)
*
* Copyright (c) 2013 Goran Mrzljak
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.turbogerm.helljump.game.generator;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.turbogerm.helljump.dataaccess.EnemyData;
import com.turbogerm.helljump.dataaccess.ItemData;
import com.turbogerm.helljump.dataaccess.PlatformData;
import com.turbogerm.helljump.dataaccess.RiseSectionData;
import com.turbogerm.helljump.dataaccess.RiseSectionDataBase;
import com.turbogerm.helljump.dataaccess.RiseSectionMetadata;
import com.turbogerm.helljump.dataaccess.RiseSectionMetadataReader;
import com.turbogerm.helljump.dataaccess.RiseSectionsData;
import com.turbogerm.helljump.dataaccess.RiseSectionsDataReader;
import com.turbogerm.helljump.dataaccess.RiseSectionsMetadata;
import com.turbogerm.helljump.game.GameAreaUtils;
import com.turbogerm.helljump.game.Rise;
import com.turbogerm.helljump.game.RiseSection;
import com.turbogerm.helljump.game.enemies.EnemyBase;
import com.turbogerm.helljump.game.enemies.EnemyFactory;
import com.turbogerm.helljump.game.items.ItemBase;
import com.turbogerm.helljump.game.items.ItemFactory;
import com.turbogerm.helljump.game.platforms.PlatformBase;
import com.turbogerm.helljump.game.platforms.PlatformFactory;
import com.turbogerm.helljump.resources.ResourceNames;
public final class RiseGenerator {
private static final int RISE_HEIGHT_STEPS = 5000;
private static final int RISE_TRESHOLD_4 = 800;
private static final int RISE_TRESHOLD_10 = 4000;
private static final int RISE_LOWER_DIFFICULTY_STEP = RISE_TRESHOLD_4 / 5;
private static final int RISE_HIGHER_DIFFICULTY_STEP = (RISE_TRESHOLD_10 - RISE_TRESHOLD_4) / 6;
private static final float STANDARD_SECTION_WEIGHT = 1.0f;
private static final float ENEMY_SECTION_WEIGHT = 1.0f;
private static final float SPECIAL_SECTION_WEIGHT = 1.0f;
private static final float STANDARD_SECTION_CUMULATIVE_FRACTION;
private static final float ENEMY_SECTION_CUMULATIVE_FRACTION;
private static final int RISE_SECTIONS_INITIAL_CAPACITY = 400;
private static final RiseSectionsData PREBUILT_RISE_SECTIONS;
private static final RiseSectionsMetadata RISE_SECTIONS_METADATA;
private static final Array<RiseSectionDataBase> TRANSITION_RISE_SECTIONS;
private static final Array<RiseSectionDataBase> STANDARD_RISE_SECTIONS;
private static final Array<RiseSectionDataBase> ENEMY_RISE_SECTIONS;
private static final Array<RiseSectionDataBase> SPECIAL_RISE_SECTIONS;
private static final int RISE_SECTION_TYPE_INITIAL_CAPACITY = 10;
private static final Array<RiseSectionDataBase> RISE_SECTION_SELECTION_LIST;
private static final int RISE_SECTION_SELECTION_LIST_INITIAL_CAPACITY = 10;
private static final int TRANSITION_SECTION_TYPE = 0;
private static final int STANDARD_SECTION_TYPE = 1;
private static final int ENEMY_SECTION_TYPE = 2;
private static final int SPECIAL_SECTION_TYPE = 3;
static {
float totalSectionWeight = STANDARD_SECTION_WEIGHT + ENEMY_SECTION_WEIGHT + SPECIAL_SECTION_WEIGHT;
STANDARD_SECTION_CUMULATIVE_FRACTION = STANDARD_SECTION_WEIGHT / totalSectionWeight;
ENEMY_SECTION_CUMULATIVE_FRACTION = STANDARD_SECTION_CUMULATIVE_FRACTION + ENEMY_SECTION_WEIGHT /
totalSectionWeight;
PREBUILT_RISE_SECTIONS = RiseSectionsDataReader.read(Gdx.files.internal(ResourceNames.RISE_SECTIONS_DATA));
RISE_SECTIONS_METADATA = RiseSectionMetadataReader.read(
Gdx.files.internal(ResourceNames.RISE_SECTIONS_METADATA));
// initialize all rise sections list
Array<RiseSectionData> prebuiltRiseSections = PREBUILT_RISE_SECTIONS.getAllRiseSections();
Array<RiseSectionMetadata> riseSectionsMetadata = RISE_SECTIONS_METADATA.getAllRiseSections();
int allRiseSectionsCount = PREBUILT_RISE_SECTIONS.getRiseSectionCount() +
RISE_SECTIONS_METADATA.getRiseSectionCount();
Array<RiseSectionDataBase> allRiseSections = new Array<RiseSectionDataBase>(true, allRiseSectionsCount);
allRiseSections.addAll(prebuiltRiseSections);
allRiseSections.addAll(riseSectionsMetadata);
// separate rise section types
TRANSITION_RISE_SECTIONS = new Array<RiseSectionDataBase>(false, RISE_SECTION_TYPE_INITIAL_CAPACITY);
STANDARD_RISE_SECTIONS = new Array<RiseSectionDataBase>(false, RISE_SECTION_TYPE_INITIAL_CAPACITY);
ENEMY_RISE_SECTIONS = new Array<RiseSectionDataBase>(false, RISE_SECTION_TYPE_INITIAL_CAPACITY);
SPECIAL_RISE_SECTIONS = new Array<RiseSectionDataBase>(false, RISE_SECTION_TYPE_INITIAL_CAPACITY);
for (RiseSectionDataBase riseSectionDataBase : allRiseSections) {
String type = riseSectionDataBase.getType();
if (RiseSectionDataBase.isTransitionType(type)) {
TRANSITION_RISE_SECTIONS.add(riseSectionDataBase);
} else if (RiseSectionDataBase.isStandardType(type)) {
STANDARD_RISE_SECTIONS.add(riseSectionDataBase);
} else if (RiseSectionDataBase.isEnemyType(type)) {
ENEMY_RISE_SECTIONS.add(riseSectionDataBase);
} else if (RiseSectionDataBase.isSpecialType(type)) {
SPECIAL_RISE_SECTIONS.add(riseSectionDataBase);
}
}
RISE_SECTION_SELECTION_LIST = new Array<RiseSectionDataBase>(
false, RISE_SECTION_SELECTION_LIST_INITIAL_CAPACITY);
}
public static Rise generate(AssetManager assetManager) {
Array<RiseSectionData> riseSectionsData = new Array<RiseSectionData>(true, RISE_SECTIONS_INITIAL_CAPACITY);
int stepsInRise = 0;
RiseSectionData currRiseSection;
// String typePrefix = "normal";
// for (int i = 1; i <= 5; i++) {
// String sectionName = String.format(typePrefix + "%02d", i);
// currRiseSection = RiseSectionGenerator.generateRiseSection(
// RISE_SECTIONS_METADATA.getByName(sectionName));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
// }
// currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("transition03"));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
//
// currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("visibleonjumpnormal"));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
//
// currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("visibleonjumpcrumble"));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
//
// currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("crumble"));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
// currRiseSection = PREBUILT_RISE_SECTIONS.getRiseSection("testitems00");
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
// for (RiseSectionDataBase riseSectionData : ENEMY_RISE_SECTIONS) {
// String name = riseSectionData.getName();
// if (!name.startsWith("knight")) {
// continue;
// }
//
// currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("transition03"));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
//
// currRiseSection = PREBUILT_RISE_SECTIONS.getRiseSection(name);
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
// }
//
// for (RiseSectionDataBase riseSectionData : SPECIAL_RISE_SECTIONS) {
// String name = riseSectionData.getName();
//
// currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("transition03"));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
//
// currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName(name));
// riseSectionsData.add(currRiseSection);
// stepsInRise += currRiseSection.getStepRange();
// }
currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("initial0"));
riseSectionsData.add(currRiseSection);
stepsInRise += currRiseSection.getStepRange();
currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("initial1"));
riseSectionsData.add(currRiseSection);
stepsInRise += currRiseSection.getStepRange();
currRiseSection = RiseSectionGenerator.generateRiseSection(RISE_SECTIONS_METADATA.getByName("initial2"));
riseSectionsData.add(currRiseSection);
stepsInRise += currRiseSection.getStepRange();
boolean isTransitionSection = true;
while (stepsInRise < RISE_HEIGHT_STEPS) {
currRiseSection = getRandomRiseSection(stepsInRise, isTransitionSection);
riseSectionsData.add(currRiseSection);
stepsInRise += currRiseSection.getStepRange();
isTransitionSection = !isTransitionSection;
}
adjustLastRiseSection(riseSectionsData);
// TODO: only for debugging
String[] riseSectionNames = new String[riseSectionsData.size];
for (int i = 0; i < riseSectionNames.length; i++) {
riseSectionNames[i] = riseSectionsData.get(i).getName();
}
Array<RiseSection> riseSections = getRiseSections(riseSectionsData, assetManager);
return new Rise(riseSections);
}
private static RiseSectionData getRandomRiseSection(int stepsInRise, boolean isTransitionSection) {
int sectionType;
boolean revertToStandard;
if (isTransitionSection) {
sectionType = TRANSITION_SECTION_TYPE;
revertToStandard = false;
} else {
float randomRiseSectionNumber = MathUtils.random();
if (randomRiseSectionNumber <= STANDARD_SECTION_CUMULATIVE_FRACTION) {
sectionType = STANDARD_SECTION_TYPE;
revertToStandard = false;
} else if (randomRiseSectionNumber <= ENEMY_SECTION_CUMULATIVE_FRACTION) {
sectionType = ENEMY_SECTION_TYPE;
revertToStandard = true;
} else {
sectionType = SPECIAL_SECTION_TYPE;
revertToStandard = true;
}
}
Array<RiseSectionDataBase> riseSectionList = getRiseSectionList(sectionType);
MinMaxDifficulty minMaxDifficulty = getRiseSectionMinMaxDifficulty(sectionType, stepsInRise);
RiseSectionData riseSectionData = getRandomRiseSectionData(
minMaxDifficulty.minDifficulty, minMaxDifficulty.maxDifficulty, riseSectionList, revertToStandard);
return riseSectionData;
}
private static Array<RiseSectionDataBase> getRiseSectionList(int sectionType) {
switch (sectionType) {
case TRANSITION_SECTION_TYPE:
return TRANSITION_RISE_SECTIONS;
case STANDARD_SECTION_TYPE:
return STANDARD_RISE_SECTIONS;
case ENEMY_SECTION_TYPE:
return ENEMY_RISE_SECTIONS;
case SPECIAL_SECTION_TYPE:
return SPECIAL_RISE_SECTIONS;
default:
return TRANSITION_RISE_SECTIONS;
}
}
private static MinMaxDifficulty getRiseSectionMinMaxDifficulty(int sectionType, int stepsInRise) {
int difficulty;
if (stepsInRise <= RISE_TRESHOLD_4) {
difficulty = MathUtils.clamp(stepsInRise / RISE_LOWER_DIFFICULTY_STEP, 1, 4);
} else if (stepsInRise <= RISE_TRESHOLD_10) {
difficulty = Math.min(5 + (stepsInRise - RISE_TRESHOLD_4) / RISE_HIGHER_DIFFICULTY_STEP, 10);
} else {
difficulty = 10;
}
int minDifficulty;
int maxDifficulty;
switch (sectionType) {
case TRANSITION_SECTION_TYPE:
minDifficulty = difficulty;
maxDifficulty = difficulty;
break;
case STANDARD_SECTION_TYPE:
if (difficulty <= 4) {
minDifficulty = difficulty;
} else {
minDifficulty = Math.max(difficulty - 2, 4);
}
maxDifficulty = Math.min(difficulty, 10);
break;
case ENEMY_SECTION_TYPE:
minDifficulty = Math.min(difficulty, 4);
maxDifficulty = difficulty;
break;
case SPECIAL_SECTION_TYPE:
minDifficulty = Math.min(difficulty, 4);
maxDifficulty = difficulty;
break;
default:
minDifficulty = 0;
maxDifficulty = 10;
break;
}
return new MinMaxDifficulty(minDifficulty, maxDifficulty);
}
private static RiseSectionData getRandomRiseSectionData(int minDifficulty, int maxDifficulty,
Array<RiseSectionDataBase> riseSectionList, boolean revertToStandard) {
if (maxDifficulty < 0) {
return null;
}
RISE_SECTION_SELECTION_LIST.clear();
for (RiseSectionDataBase riseSectionDataBase : riseSectionList) {
int difficulty = riseSectionDataBase.getDifficulty();
if (minDifficulty <= difficulty && difficulty <= maxDifficulty) {
RISE_SECTION_SELECTION_LIST.add(riseSectionDataBase);
}
}
if (RISE_SECTION_SELECTION_LIST.size > 0) {
return getRiseSectionData(RISE_SECTION_SELECTION_LIST.random());
} else {
for (RiseSectionDataBase riseSectionDataBase : riseSectionList) {
if (RISE_SECTION_SELECTION_LIST.size == 0) {
RISE_SECTION_SELECTION_LIST.add(riseSectionDataBase);
} else {
if (revertToStandard) {
return getRandomRiseSectionData(minDifficulty, maxDifficulty, STANDARD_RISE_SECTIONS, false);
} else {
RiseSectionDataBase selectedRiseSectionDataBase = RISE_SECTION_SELECTION_LIST.first();
int currentDist = getDifficultyDistance(
selectedRiseSectionDataBase.getDifficulty(), minDifficulty, maxDifficulty);
int newDist = getDifficultyDistance(
riseSectionDataBase.getDifficulty(), minDifficulty, maxDifficulty);
if (currentDist == newDist) {
RISE_SECTION_SELECTION_LIST.add(riseSectionDataBase);
} else if (isNewDifficultyDistanceBetter(currentDist, newDist)) {
RISE_SECTION_SELECTION_LIST.clear();
RISE_SECTION_SELECTION_LIST.add(riseSectionDataBase);
}
}
}
}
return getRiseSectionData(RISE_SECTION_SELECTION_LIST.random());
}
}
private static RiseSectionData getRiseSectionData(RiseSectionDataBase riseSectionDataBase) {
if (riseSectionDataBase.isMetadata()) {
return RiseSectionGenerator.generateRiseSection((RiseSectionMetadata) riseSectionDataBase);
} else {
return (RiseSectionData) riseSectionDataBase;
}
}
private static int getDifficultyDistance(int difficulty, int minDifficulty, int maxDifficulty) {
if (difficulty < minDifficulty) {
return difficulty - minDifficulty;
} else if (difficulty > maxDifficulty) {
return difficulty - maxDifficulty;
} else {
return 0;
}
}
private static boolean isNewDifficultyDistanceBetter(int currentDist, int newDist) {
return (currentDist < 0 && newDist < 0 && newDist > currentDist) ||
(currentDist > 0 && newDist > 0 && newDist < currentDist) ||
(newDist < 0 && currentDist > 0);
}
private static void adjustLastRiseSection(Array<RiseSectionData> riseSectionsData) {
RiseSectionData lastRiseSectionData = riseSectionsData.pop();
int lastPlatformStep = lastRiseSectionData.getPlatformsData().peek().getStep();
int newStepRange = lastPlatformStep + PlatformData.MAX_PLATFORM_DISTANCE_STEPS;
// make sure there is maximum platform distance between last platform and end line
// this way no platforms will be seen on end background texture
RiseSectionData newLastRiseSectionData = new RiseSectionData(
lastRiseSectionData.getType(),
lastRiseSectionData.getName(),
newStepRange,
lastRiseSectionData.getDifficulty(),
lastRiseSectionData.getPlatformsData(),
lastRiseSectionData.getEnemiesData(),
lastRiseSectionData.getItemsData());
riseSectionsData.add(newLastRiseSectionData);
}
private static Array<RiseSection> getRiseSections(Array<RiseSectionData> riseSectionsData,
AssetManager assetManager) {
Array<RiseSection> riseSections = new Array<RiseSection>(true, riseSectionsData.size);
int id = 0;
int startStep = 0;
for (RiseSectionData riseSectionData : riseSectionsData) {
RiseSection riseSection = getRiseSection(id, startStep, riseSectionData, assetManager);
riseSections.add(riseSection);
id++;
startStep += riseSectionData.getStepRange();
}
return riseSections;
}
private static RiseSection getRiseSection(int riseSectionId, int startStep, RiseSectionData riseSectionData,
AssetManager assetManager) {
String riseSectionName = riseSectionData.getName();
int difficulty = riseSectionData.getDifficulty();
float startY = startStep * GameAreaUtils.STEP_HEIGHT;
float height = riseSectionData.getStepRange() * GameAreaUtils.STEP_HEIGHT;
Array<PlatformData> platformsData = riseSectionData.getPlatformsData();
Array<PlatformBase> platforms = new Array<PlatformBase>(true, platformsData.size);
for (PlatformData platformData : platformsData) {
PlatformBase platform = PlatformFactory.create(riseSectionId, platformData, startStep, assetManager);
platforms.add(platform);
}
Array<EnemyData> enemiesData = riseSectionData.getEnemiesData();
Array<EnemyBase> enemies;
if (enemiesData != null) {
enemies = new Array<EnemyBase>(true, enemiesData.size);
for (EnemyData enemyData : enemiesData) {
EnemyBase enemy = EnemyFactory.create(enemyData, startStep, assetManager);
enemies.add(enemy);
}
} else {
enemies = new Array<EnemyBase>(true, 0);
}
Array<ItemData> itemsData = riseSectionData.getItemsData();
Array<ItemBase> items;
if (itemsData != null) {
items = new Array<ItemBase>(true, itemsData.size);
for (ItemData itemData : itemsData) {
if (MathUtils.random() > itemData.getAppearanceChance()) {
continue;
}
ItemBase item = ItemFactory.create(itemData, startStep, assetManager);
int attachedToPlatformId = itemData.getAttachedToPlatformId();
if (attachedToPlatformId >= 0) {
PlatformBase attachedToPlatform = getPlatform(attachedToPlatformId, platforms);
attachedToPlatform.attachItem(item);
}
items.add(item);
}
} else {
items = new Array<ItemBase>(true, 0);
}
return new RiseSection(riseSectionId, riseSectionName, difficulty, startY, height, platforms, enemies, items);
}
private static PlatformBase getPlatform(int platformId, Array<PlatformBase> platforms) {
for (PlatformBase platform : platforms) {
if (platform.getPlatformId() == platformId) {
return platform;
}
}
return null;
}
private static class MinMaxDifficulty {
public final int minDifficulty;
public final int maxDifficulty;
public MinMaxDifficulty(int minDifficulty, int maxDifficulty) {
this.minDifficulty = minDifficulty;
this.maxDifficulty = maxDifficulty;
}
}
}